#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedGossip.h"
#include "Player.h"
#include "SpellInfo.h"
#include "WorldSession.h"
#include "Group.h"
#include "InstanceSaveMgr.h"
#include "InstanceScript.h"
#include "MapManager.h"
#pragma execution_character_set("utf-8")
#include "Cfbg.h"
#include "Battleground.h"
#include "BattlegroundMgr.h"
#include "Player.h"
#include "Chat.h"
#include "BattlegroundQueue.h"

class ada : public ItemScript
{
public:
	ada() : ItemScript("ada") { }

	bool OnUse(Player* player, Item* item, SpellCastTargets const& /*targets*/) override
	{
		player->PlayerTalkClass->ClearMenus();
		player->ADD_GOSSIP_ITEM(1, "重置副本", 1, GOSSIP_ACTION_INFO_DEF + 1);
		player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, item->GetGUID());
		return false;
	}

	void OnGossipSelect(Player* player, Item* item, uint32 sender, uint32 action) override
	{
		uint16 MapId = 0;
		int8 diff = -1;
		switch (action)
		{
		case GOSSIP_ACTION_INFO_DEF + 1:
			for (uint8 i = 0; i < MAX_DIFFICULTY; ++i)
		{
			BoundInstancesMap const& m_boundInstances = sInstanceSaveMgr->PlayerGetBoundInstances(player->GetGUIDLow(), Difficulty(i));
			for (BoundInstancesMap::const_iterator itr = m_boundInstances.begin(); itr != m_boundInstances.end();)
			{
				InstanceSave* save = itr->second.save;
				if (itr->first != player->GetMapId() && (!MapId || MapId == itr->first) && (diff == -1 || diff == save->GetDifficulty()))
				{
					sInstanceSaveMgr->PlayerUnbindInstance(player->GetGUIDLow(), itr->first, Difficulty(i), true, player);
					itr = m_boundInstances.begin();
				}
				else
					++itr;
			}
		}
		player->GetSession()->SendAreaTriggerMessage("副本已全部[重置]");
		player->CLOSE_GOSSIP_MENU();
		}
	}
};


/*####################################################################################
###############################CROSSFACTION BATTLEGROUNDS#############################
####################################################################################*/

uint8 Unit::getRace(bool forceoriginal) const
{
       if (GetTypeId() == TYPEID_PLAYER)
       {
               Player* pPlayer = ((Player*)this);

               if (forceoriginal)
                       return pPlayer->getCFSRace();

               if (pPlayer->InArena())
                       return GetByteValue(UNIT_FIELD_BYTES_0, 0);

               if (!pPlayer->IsPlayingNative())
                       return pPlayer->getFRace();
       }

       return GetByteValue(UNIT_FIELD_BYTES_0, 0);
}

bool Player::SendRealNameQuery()
{
       if (IsPlayingNative())
               return false;

       WorldPacket data(SMSG_NAME_QUERY_RESPONSE, (8 + 1 + 1 + 1 + 1 + 1 + 10));
       data.appendPackGUID(GetGUID());                             // player guid
       data << uint8(0);                                       // added in 3.1; if > 1, then end of packet
       data << GetName();                                   // played name
       data << uint8(0);                                       // realm name for cross realm BG usage
       data << uint8(getCFSRace());
       data << uint8(getGender());
       data << uint8(getClass());
       data << uint8(0);                                   // is not declined
       GetSession()->SendPacket(&data);

       return true;
}

void Player::SetFakeRaceAndMorph()
{
       if (getClass() == CLASS_DRUID)
       {
               if (GetCFSTeam() == ALLIANCE)
               {
                       m_FakeMorph = getGender() == GENDER_MALE ? FAKE_M_TAUREN : FAKE_F_TAUREN;
                       m_FakeRace = RACE_TAUREN;
               }
               else if (getGender() == GENDER_MALE) // HORDE PLAYER, ONLY HAVE MALE NELF ID
               {
                       m_FakeMorph = FAKE_M_NELF;
                       m_FakeRace = RACE_NIGHTELF;
               }
               else
                       m_FakeRace = GetCFSTeam() == ALLIANCE ? RACE_BLOODELF : RACE_HUMAN;
       }
       else if (getClass() == CLASS_SHAMAN && GetCFSTeam() == HORDE && getGender() == GENDER_FEMALE)
       {
               m_FakeMorph = FAKE_F_DRAENEI; // Female Draenei
               m_FakeRace = RACE_DRAENEI;
       }
       else
       {
               m_FakeRace = GetCFSTeam() == ALLIANCE ? RACE_BLOODELF : RACE_HUMAN;

               if (GetCFSTeam() == HORDE)
               {
                       if (getGender() == GENDER_MALE)
                               m_FakeMorph = 19723;
                       else
                               m_FakeMorph = 19724;
               }
               else
               {
                       if (getGender() == GENDER_MALE)
                               m_FakeMorph = 20578;
                       else
                               m_FakeMorph = 20579;
               }
       }
}

bool Player::SendBattleGroundChat(uint32 msgtype, std::string message)
{
    // Select distance to broadcast to.
    float distance = msgtype == CHAT_MSG_SAY ? sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY) : sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL);

    if (Battleground* bg = GetBattleground())
    {
        if (bg->isArena()) // Only fake chat in BG's. CFBG should not interfere with arenas.
            return false;

        for (Battleground::BattlegroundPlayerMap::const_iterator itr = bg->GetPlayers().begin(); itr != bg->GetPlayers().end(); ++itr)
        {
            if (Player* player = ObjectAccessor::FindPlayer(itr->first))
            {
                if (GetDistance2d(player->GetPositionX(), player->GetPositionY()) <= distance)
                {
                    WorldPacket data(SMSG_MESSAGECHAT, 200);

                    if (GetTeamId() == player->GetTeamId())
                        BuildPlayerChat(&data, msgtype, message, LANG_UNIVERSAL);
                    else if (msgtype != CHAT_MSG_EMOTE)
                       BuildPlayerChat(&data, msgtype, message, player->GetTeamId() == ALLIANCE ? LANG_ORCISH : LANG_COMMON);

                    player->GetSession()->SendPacket(&data);
                }
            }
        }
        return true;
    }
    else
               return false;
}

void Player::MorphFit(bool value)
{
       if (!IsPlayingNative() && value)
       {
               SetDisplayId(GetFakeMorph());
               SetNativeDisplayId(GetFakeMorph());
       }
       else
               InitDisplayIds();
}

void Player::FitPlayerInTeam(bool action, Battleground* pBattleGround)
{
       if (!pBattleGround)
               pBattleGround = GetBattleground();

       if ((!pBattleGround || pBattleGround->isArena()) && action)
               return;

       if (!IsPlayingNative() && action)
               setFactionForRace(getRace());
       else
               setFactionForRace(getCFSRace());

       if (action)
               SetForgetBGPlayers(true);
       else
              SetForgetInListPlayers(true);

       MorphFit(action);

       if (pBattleGround && action)
              SendChatMessage("%sYou are playing for the %s%s in this %s", MSG_COLOR_WHITE, GetTeamId() == ALLIANCE ? MSG_COLOR_DARKBLUE"alliance" : MSG_COLOR_RED"horde", MSG_COLOR_WHITE, pBattleGround->GetName());
}

void Player::DoForgetPlayersInList()
{
       // m_FakePlayers is filled from a vector within the battleground
       // they were in previously so all players that have been in that BG will be invalidated.
       for (FakePlayers::const_iterator itr = m_FakePlayers.begin(); itr != m_FakePlayers.end(); ++itr)
       {
               WorldPacket data(SMSG_INVALIDATE_PLAYER, 8);
               data << *itr;
               GetSession()->SendPacket(&data);
               //if (Player* pPlayer = ObjectAccessor::FindPlayer(ObjectGuid(*itr)))
			      if (Player* pPlayer = ObjectAccessor::FindPlayer(*itr))
                       GetSession()->SendNameQueryOpcode(pPlayer->GetGUID());
       }
       m_FakePlayers.clear();
}

void Player::DoForgetPlayersInBG(Battleground* pBattleGround)
{
       for (Battleground::BattlegroundPlayerMap::const_iterator itr = pBattleGround->GetPlayers().begin(); itr != pBattleGround->GetPlayers().end(); ++itr)
       {
               // Here we invalidate players in the bg to the added player
               WorldPacket data1(SMSG_INVALIDATE_PLAYER, 8);
               data1 << itr->first;
               GetSession()->SendPacket(&data1);

               if (Player* pPlayer = ObjectAccessor::FindPlayer(itr->first))
               {
                       GetSession()->SendNameQueryOpcode(pPlayer->GetGUID()); // Send namequery answer instantly if player is available
                       // Here we invalidate the player added to players in the bg
                       WorldPacket data2(SMSG_INVALIDATE_PLAYER, 8);
                       data2 << GetGUID();
                       pPlayer->GetSession()->SendPacket(&data2);
                       pPlayer->GetSession()->SendNameQueryOpcode(GetGUID());
               }
       }
}

bool BattlegroundQueue::CheckCrossFactionMatch(BattlegroundBracketId bracket_id, Battleground* bg)
{
       if (!sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS) || bg->isArena())
               return false; // Only do this if crossbg's are enabled.

       // Here we will add all players to selectionpool, later we check if there are enough and launch a bg.
       FillXPlayersToBG(bracket_id, bg, true);

       if (sBattlegroundMgr->isTesting() && (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() || m_SelectionPools[TEAM_HORDE].GetPlayerCount()))
               return true;

       uint8 MPT = bg->GetMinPlayersPerTeam();
       if (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() < MPT || m_SelectionPools[TEAM_HORDE].GetPlayerCount() < MPT)
               return false;

       return true;
}

// This function will invite players in the least populated faction, which makes battleground queues much faster.
// This function will return true if cross faction battlegrounds are enabled, otherwise return false,
// which is useful in FillPlayersToBG. Because then we can interrupt the regular invitation if cross faction bg's are enabled.
bool BattlegroundQueue::FillXPlayersToBG(BattlegroundBracketId bracket_id, Battleground* bg, bool start)
{
       uint8 queuedPeople = 0;
       for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr)
               if (!(*itr)->IsInvitedToBGInstanceGUID)
                       queuedPeople += (*itr)->Players.size();

               if (sWorld->getBoolConfig(CROSSFACTION_SYSTEM_BATTLEGROUNDS) && (sBattlegroundMgr->isTesting() || queuedPeople >= bg->GetMinPlayersPerTeam() * 2 || !start))
       {
		   int32 aliFree = start ? bg->GetMaxPlayersPerTeam() : bg->GetFreeSlotsForTeam(TEAM_ALLIANCE);
		   int32 hordeFree = start ? bg->GetMaxPlayersPerTeam() : bg->GetFreeSlotsForTeam(TEAM_HORDE);
              // Empty selection pools. They will be refilled from queued groups.
               m_SelectionPools[TEAM_ALLIANCE].Init();
               m_SelectionPools[TEAM_HORDE].Init();
               int32 valiFree = aliFree;
               int32 vhordeFree = hordeFree;
               int32 diff = 0;


               // Add teams to their own factions as far as possible.
               if (start)
               {
                       QueuedGroupMap m_PreGroupMap_a, m_PreGroupMap_h;
                       int32 m_SmallestOfTeams = 0;
                       int32 queuedAlliance = 0;
                       int32 queuedHorde = 0;

                       for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr)
                       {
                               if ((*itr)->IsInvitedToBGInstanceGUID)
                                       continue;

                               bool alliance = (*itr)->CFSTeam == ALLIANCE;

                               if (alliance)
                               {
                                       m_PreGroupMap_a.insert(std::make_pair((*itr)->Players.size(), *itr));
                                       queuedAlliance += (*itr)->Players.size();
                               }
                               else
                               {
                                       m_PreGroupMap_h.insert(std::make_pair((*itr)->Players.size(), *itr));
                                       queuedHorde += (*itr)->Players.size();
                               }
                       }

                       m_SmallestOfTeams = std::min(std::min(aliFree, queuedAlliance), std::min(hordeFree, queuedHorde));

                       valiFree -= PreAddPlayers(m_PreGroupMap_a, m_SmallestOfTeams, aliFree);
                       vhordeFree -= PreAddPlayers(m_PreGroupMap_h, m_SmallestOfTeams, hordeFree);
               }
               QueuedGroupMap m_QueuedGroupMap;

               for (GroupsQueueType::const_iterator itr = m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].begin(); itr != m_QueuedGroups[bracket_id][BG_QUEUE_CROSSFACTION].end(); ++itr)
                       m_QueuedGroupMap.insert(std::make_pair((*itr)->Players.size(), *itr));

               for (QueuedGroupMap::reverse_iterator itr = m_QueuedGroupMap.rbegin(); itr != m_QueuedGroupMap.rend(); ++itr)
               {
                       GroupsQueueType allypool = m_SelectionPools[TEAM_ALLIANCE].SelectedGroups;
                       GroupsQueueType hordepool = m_SelectionPools[TEAM_HORDE].SelectedGroups;

                       GroupQueueInfo* ginfo = itr->second;

                       // If player already was invited via pre adding (add to own team first) or he was already invited to a bg, skip.
                       if (ginfo->IsInvitedToBGInstanceGUID ||
                               std::find(allypool.begin(), allypool.end(), ginfo) != allypool.end() ||
                               std::find(hordepool.begin(), hordepool.end(), ginfo) != hordepool.end() ||
                               (m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() >= bg->GetMinPlayersPerTeam() &&
                               m_SelectionPools[TEAM_HORDE].GetPlayerCount() >= bg->GetMinPlayersPerTeam()))
                               continue;

                       diff = abs(valiFree - vhordeFree);
                       bool moreAli = valiFree < vhordeFree;

                       if (diff > 0)
						   ginfo->teamId = moreAli ? TEAM_HORDE : TEAM_ALLIANCE;

					   bool alliance = ginfo->teamId == ALLIANCE;

                       if (m_SelectionPools[alliance ? TEAM_ALLIANCE : TEAM_HORDE].AddGroup(ginfo, alliance ? aliFree : hordeFree))
                               alliance ? valiFree -= ginfo->Players.size() : vhordeFree -= ginfo->Players.size();
               }

               return true;
       }
       return false;
}

int32 BattlegroundQueue::PreAddPlayers(QueuedGroupMap m_PreGroupMap, int32 MaxAdd, uint32 MaxInTeam)
{
       int32 LeftToAdd = MaxAdd;
       uint32 Added = 0;

       for (QueuedGroupMap::reverse_iterator itr = m_PreGroupMap.rbegin(); itr != m_PreGroupMap.rend(); ++itr)
       {
               int32 PlayerSize = itr->first;
               bool alliance = itr->second->CFSTeam == ALLIANCE;

               if (PlayerSize <= LeftToAdd && m_SelectionPools[alliance ? TEAM_ALLIANCE : TEAM_HORDE].AddGroup(itr->second, MaxInTeam))
                       LeftToAdd -= PlayerSize, Added -= PlayerSize;
       }

       return LeftToAdd;
}

void Player::SendChatMessage(const char *format, ...)
{
       if (!IsInWorld())
               return;

       if (format)
       {
               va_list ap;
               char str[2048];
               va_start(ap, format);
               vsnprintf(str, 2048, format, ap);
               va_end(ap);

               ChatHandler(GetSession()).SendSysMessage(str);
       }
}


/*####################################################################################
###############################CROSSFACTION BATTLEGROUNDS#############################
####################################################################################*/


void AddadaScripts()
{
	new ada;
}